//------------------------------------------------------------------------------
//  EffectManager.cpp
//  (C) 2009 xoyojank
//------------------------------------------------------------------------------
#include "StdEddy.h"
#include "Graphics/EffectManager.h"
#include "Graphics/VertexElement.h"
#include "Common/FileSystem.h"
#include "Common/FileStream.h"
#include "Common/StringUtil.h"

//------------------------------------------------------------------------------
namespace Eddy
{

const WString ShaderPath = L"shader\\";

//------------------------------------------------------------------------------
EffectManager::EffectManager()
    : isOpen(false)
{
    // empty
}

//------------------------------------------------------------------------------
EffectManager::~EffectManager()
{
    assert(!this->IsOpen());
}

//------------------------------------------------------------------------------
bool
EffectManager::Open()
{
    assert(!this->IsOpen());

    // create all the shaders under shader directory
    this->LoadShaders(this->vertexShaderMap, GL_VERTEX_SHADER, L"*.vp.bin");
    this->LoadShaders(this->pixelShaderMap, GL_FRAGMENT_SHADER, L"*.fp.bin");

    this->isOpen = true;

    return this->isOpen;
}

//------------------------------------------------------------------------------
void
EffectManager::Close()
{
    assert(this->IsOpen());

    // delete effects
    this->effectMap.clear();
    // delete vertex shaders
    for (std::map<WString, GLuint>::iterator it = this->vertexShaderMap.begin();
            it != this->vertexShaderMap.end(); ++it)
    {
        GLuint shaderId = it->second;
        glDeleteShader(shaderId);
    }
    this->vertexShaderMap.clear();
    // delete fragment shaders
    for (std::map<WString, GLuint>::iterator it = this->pixelShaderMap.begin();
            it != this->pixelShaderMap.end(); ++it)
    {
        GLuint shaderId = it->second;
        glDeleteShader(shaderId);
    }
    this->pixelShaderMap.clear();

    this->isOpen = false;
}

//------------------------------------------------------------------------------
inline const EffectId&
EffectManager::GetEffectId(const WString& vs, const WString& ps) const
{
    do
    {
        static EffectId effectId;
        std::map<WString, GLuint>::const_iterator vpIt = this->vertexShaderMap.find(vs);
        if (this->vertexShaderMap.end() == vpIt)
            break;
        std::map<WString, GLuint>::const_iterator fpIt = this->pixelShaderMap.find(ps);
        if (this->pixelShaderMap.end() == fpIt)
            break;

        effectId.vsId = vpIt->second;
        effectId.psId = fpIt->second;

        return effectId;
    }
    while (0);

    // vs/ps is not exist
    return EffectId::InvalidId;
}

//------------------------------------------------------------------------------
const EffectPtr&
EffectManager::CreateEffect(const WString& vs, const WString& ps)
{
    const EffectId& effectId = this->GetEffectId(vs, ps);
    if (EffectId::InvalidId == effectId)
    {
        assert(false);
        static const EffectPtr nullPtr;
        return nullPtr;
    }
    return this->CreateEffect(effectId);
}

//------------------------------------------------------------------------------
const EffectPtr&
EffectManager::CreateEffect(const EffectId& effectId)
{
    std::map<EffectId, EffectPtr>::iterator it = this->effectMap.find(effectId);
    if (this->effectMap.end() != it)
    {
        // already created
        return it->second;
    }

    // not exist, create a new one
    GLuint program = glCreateProgram();
    assert(0 != program);

    glAttachShader(program, effectId.vsId);
    glAttachShader(program, effectId.psId);
    // bind attributes
    glBindAttribLocation(program, 0, VertexElement::GetGLAttrName(VertexElement::Position, 0));
    glBindAttribLocation(program, 1, VertexElement::GetGLAttrName(VertexElement::Normal, 0));
    glBindAttribLocation(program, 2, VertexElement::GetGLAttrName(VertexElement::Diffuse, 0));
    glBindAttribLocation(program, 3, VertexElement::GetGLAttrName(VertexElement::Specular, 0));
    glBindAttribLocation(program, 4, VertexElement::GetGLAttrName(VertexElement::TexCoord, 0));
    glBindAttribLocation(program, 5, VertexElement::GetGLAttrName(VertexElement::TexCoord, 1));
    glBindAttribLocation(program, 6, VertexElement::GetGLAttrName(VertexElement::TexCoord, 2));
    glBindAttribLocation(program, 7, VertexElement::GetGLAttrName(VertexElement::TexCoord, 3));

    // link the program
    glLinkProgram(program);
    GLint success = GL_FALSE;
    glGetProgramiv(program, GL_LINK_STATUS, &success);
    if (!success)
    {
        GLint infoLength = 0;
        glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLength);
        if (infoLength > 1)
        {
            std::vector<char> infoLog(infoLength + 1, 0);
            glGetProgramInfoLog(program, infoLength, NULL, &infoLog.front());
            WString logMsg;
            StringUtil::ToUnicode(logMsg, &infoLog.front());
            MessageBox(NULL, logMsg.c_str(), NULL, 0);
        }
    }

    // create effect
    EffectPtr effect = new Effect();
    effect->program = program;
    effect->InitUniformMap();
	return this->effectMap.insert(std::make_pair(effectId, effect)).first->second;
}

//------------------------------------------------------------------------------
void
EffectManager::DestroyEffect(const WString& vs, const WString& ps)
{
    const EffectId& effectId = this->GetEffectId(vs, ps);
    if (EffectId::InvalidId == effectId)
    {
        assert(false);
        return;
    }
    this->DestroyEffect(effectId);
}

//------------------------------------------------------------------------------
void
EffectManager::DestroyEffect(const EffectId& effectId)
{
    std::map<EffectId, EffectPtr>::iterator it = this->effectMap.find(effectId);
    assert(this->effectMap.end() != it);

    this->effectMap.erase(it);
}

//------------------------------------------------------------------------------
void
EffectManager::LoadShaders(std::map<WString, GLuint>& outMap, GLenum shaderType, const WString& extension)
{
    std::vector<WString> files = FileSystem::ListFiles(ShaderPath, extension);
    for (std::vector<WString>::iterator it = files.begin(); it != files.end(); ++it)
    {
        WString filePath = ShaderPath + (*it);
        FileStream file(filePath, FileStream::ReadAccess);
        assert(file.IsOpen());

        const void* data = file.Map();
        // create shader object
        GLuint shader = glCreateShader(shaderType);
        assert(0 != shader);
        // load shader
        glShaderBinary(1, &shader, 0, data, (GLint)file.GetSize());

        file.Close();

        outMap[filePath] = shader;
    }
}

}// Eddy